// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/gpu/fake_video_decode_accelerator.h"

#include <stddef.h>
#include <string.h>

#include <memory>

#include "base/bind.h"
#include "base/location.h"
#include "base/threading/thread_task_runner_handle.h"
#include "media/base/bitstream_buffer.h"
#include "media/base/limits.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_implementation.h"
#include "ui/gl/gl_surface.h"
#include "ui/gl/gl_surface_egl.h"
#include "ui/gl/gl_surface_glx.h"

namespace media {

static const uint32_t kDefaultTextureTarget = GL_TEXTURE_2D;
// Must be at least 2 since the rendering helper will switch between textures
// and if there is only one, it will wait for the next one that will never come.
// Must also be an even number as otherwise there won't be the same amount of
// white and black frames.
static const unsigned int kNumBuffers = limits::kMaxVideoFrames + (limits::kMaxVideoFrames & 1u);

FakeVideoDecodeAccelerator::FakeVideoDecodeAccelerator(
    const gfx::Size& size,
    const MakeGLContextCurrentCallback& make_context_current_cb)
    : child_task_runner_(base::ThreadTaskRunnerHandle::Get())
    , client_(NULL)
    , make_context_current_cb_(make_context_current_cb)
    , frame_buffer_size_(size)
    , flushing_(false)
    , weak_this_factory_(this)
{
}

FakeVideoDecodeAccelerator::~FakeVideoDecodeAccelerator() { }

bool FakeVideoDecodeAccelerator::Initialize(const Config& config,
    Client* client)
{
    DCHECK(child_task_runner_->BelongsToCurrentThread());
    if (config.profile == VIDEO_CODEC_PROFILE_UNKNOWN) {
        LOG(ERROR) << "unknown codec profile";
        return false;
    }
    if (config.is_encrypted()) {
        NOTREACHED() << "encrypted streams are not supported";
        return false;
    }

    // V4L2VideoDecodeAccelerator waits until first decode call to ask for buffers
    // This class asks for it on initialization instead.
    client_ = client;
    client_->ProvidePictureBuffers(kNumBuffers, PIXEL_FORMAT_UNKNOWN, 1,
        frame_buffer_size_, kDefaultTextureTarget);
    return true;
}

void FakeVideoDecodeAccelerator::Decode(
    const BitstreamBuffer& bitstream_buffer)
{
    // We won't really read from the bitstream_buffer, close the handle.
    if (base::SharedMemory::IsHandleValid(bitstream_buffer.handle()))
        base::SharedMemory::CloseHandle(bitstream_buffer.handle());

    if (bitstream_buffer.id() < 0) {
        LOG(ERROR) << "Invalid bitstream: id=" << bitstream_buffer.id();
        client_->NotifyError(INVALID_ARGUMENT);
        return;
    }

    int bitstream_buffer_id = bitstream_buffer.id();
    queued_bitstream_ids_.push(bitstream_buffer_id);
    child_task_runner_->PostTask(
        FROM_HERE, base::Bind(&FakeVideoDecodeAccelerator::DoPictureReady, weak_this_factory_.GetWeakPtr()));
}

// Similar to UseOutputBitstreamBuffer for the encode accelerator.
void FakeVideoDecodeAccelerator::AssignPictureBuffers(
    const std::vector<PictureBuffer>& buffers)
{
    DCHECK(buffers.size() == kNumBuffers);
    DCHECK(!(buffers.size() % 2));

    // Save buffers and mark all buffers as ready for use.
    std::unique_ptr<uint8_t[]> white_data(
        new uint8_t[frame_buffer_size_.width() * frame_buffer_size_.height() * 4]);
    memset(white_data.get(), UINT8_MAX,
        frame_buffer_size_.width() * frame_buffer_size_.height() * 4);
    std::unique_ptr<uint8_t[]> black_data(
        new uint8_t[frame_buffer_size_.width() * frame_buffer_size_.height() * 4]);
    memset(black_data.get(), 0,
        frame_buffer_size_.width() * frame_buffer_size_.height() * 4);
    if (!make_context_current_cb_.Run()) {
        LOG(ERROR) << "ReusePictureBuffer(): could not make context current";
        return;
    }
    for (size_t index = 0; index < buffers.size(); ++index) {
        DCHECK_LE(1u, buffers[index].service_texture_ids().size());
        glBindTexture(GL_TEXTURE_2D, buffers[index].service_texture_ids()[0]);
        // Every other frame white and the rest black.
        uint8_t* data = index % 2 ? white_data.get() : black_data.get();
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, frame_buffer_size_.width(),
            frame_buffer_size_.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE,
            data);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glBindTexture(GL_TEXTURE_2D, 0);
        free_output_buffers_.push(buffers[index].id());
    }
    child_task_runner_->PostTask(
        FROM_HERE, base::Bind(&FakeVideoDecodeAccelerator::DoPictureReady, weak_this_factory_.GetWeakPtr()));
}

void FakeVideoDecodeAccelerator::ReusePictureBuffer(int32_t picture_buffer_id)
{
    free_output_buffers_.push(picture_buffer_id);
    child_task_runner_->PostTask(
        FROM_HERE, base::Bind(&FakeVideoDecodeAccelerator::DoPictureReady, weak_this_factory_.GetWeakPtr()));
}

void FakeVideoDecodeAccelerator::Flush()
{
    flushing_ = true;
    child_task_runner_->PostTask(
        FROM_HERE, base::Bind(&FakeVideoDecodeAccelerator::DoPictureReady, weak_this_factory_.GetWeakPtr()));
}

void FakeVideoDecodeAccelerator::Reset()
{
    while (!queued_bitstream_ids_.empty()) {
        client_->NotifyEndOfBitstreamBuffer(queued_bitstream_ids_.front());
        queued_bitstream_ids_.pop();
    }
    client_->NotifyResetDone();
}

void FakeVideoDecodeAccelerator::Destroy()
{
    while (!queued_bitstream_ids_.empty()) {
        client_->NotifyEndOfBitstreamBuffer(queued_bitstream_ids_.front());
        queued_bitstream_ids_.pop();
    }
    delete this;
}

bool FakeVideoDecodeAccelerator::TryToSetupDecodeOnSeparateThread(
    const base::WeakPtr<Client>& decode_client,
    const scoped_refptr<base::SingleThreadTaskRunner>& decode_task_runner)
{
    return false;
}

void FakeVideoDecodeAccelerator::DoPictureReady()
{
    if (flushing_ && queued_bitstream_ids_.empty()) {
        flushing_ = false;
        client_->NotifyFlushDone();
    }
    while (!free_output_buffers_.empty() && !queued_bitstream_ids_.empty()) {
        int bitstream_id = queued_bitstream_ids_.front();
        queued_bitstream_ids_.pop();
        int buffer_id = free_output_buffers_.front();
        free_output_buffers_.pop();

        const Picture picture = Picture(buffer_id, bitstream_id, gfx::Rect(frame_buffer_size_),
            gfx::ColorSpace(), false);
        client_->PictureReady(picture);
        // Bitstream no longer needed.
        client_->NotifyEndOfBitstreamBuffer(bitstream_id);
        if (flushing_ && queued_bitstream_ids_.empty()) {
            flushing_ = false;
            client_->NotifyFlushDone();
        }
    }
}

} // namespace media
